﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

using RexToy;
using RexToy.Xml;

namespace ScriptXCompiler
{
    class Program
    {
        static string startPath = string.Empty;
        static string prjFile = string.Empty;
        static string outputDir = string.Empty;
        static string outputScript = string.Empty;
        static bool singleFile = true;
        private static bool ExtractParams(string[] args)
        {
            foreach (var arg in args)
            {
                var idx = arg.IndexOf(":");
                if (idx < 0)
                {
                    return false;
                }
                string p = arg.Substring(0, idx);
                if (p == "p" || p == "-p")
                {
                    prjFile = arg.Substring(idx + 1);
                    continue;
                }
                if (p == "o" || p == "-o")
                {
                    outputDir = arg.Substring(idx + 1);
                    continue;
                }
                if (p == "s" || p == "-s")
                {
                    singleFile = bool.Parse(arg.Substring(idx + 1));
                    continue;
                }
                return false;
            }
            if (string.IsNullOrEmpty(prjFile))
                return false;
            if (string.IsNullOrEmpty(outputDir))
                return false;
            if (!File.Exists(prjFile))
            {
                Console.WriteLine("Project file not exist.");
                return false;
            }

            startPath = Path.GetDirectoryName(prjFile);
            return true;
        }

        private static void ShowMessage()
        {
            Console.WriteLine("Usage: ScriptXCompile -o:<output dir> -p:<csproj file> [-s:<true|false>]");
            Console.WriteLine(@"    Example: p:..\..\Sample\Sample.csproj o:..\..\Sample\bin\Debug");
            Console.WriteLine(@"Recommend usage, in VS project post-build event:");
            Console.WriteLine(@"    [Path]ScriptXCompile -o:$(TargetDir) -p:$(ProjectDir)$(ProjectFileName) -s:false");
        }

        private static List<string> _refPathList = new List<string>();
        private static List<string> _compilePathList = new List<string>();
        static void Main(string[] args)
        {
            if (!ExtractParams(args))
            {
                ShowMessage();
            }
            else
            {
                PrepareCompileInfo();

                ErrorAggregator aggregator = Compile();

                OutputCompileInfo(aggregator);
            }
            Console.ReadLine();
        }

        private static void OutputCompileInfo(ErrorAggregator aggregator)
        {
            if (!aggregator.HasError)
            {
                ConsoleColorController.SetForeGroundColour(ConsoleColor.Green);
                Console.WriteLine("////Compile by CSXC Success.");
                ConsoleColorController.SetForeGroundColour();
            }
            else
            {
                ConsoleColorController.SetForeGroundColour(ConsoleColor.Cyan);
                Console.WriteLine("////Issue detect when compile by CSXC");
                var count = 1;
                foreach (CompileIssue issue in aggregator)
                {
                    switch (issue.IssueType)
                    {
                        case IssueType.Error:
                            ConsoleColorController.SetForeGroundColour(ConsoleColor.Red, true);
                            break;

                        case IssueType.Warning:
                            ConsoleColorController.SetForeGroundColour(ConsoleColor.Yellow, true);
                            break;

                        default:
                            ConsoleColorController.SetForeGroundColour(ConsoleColor.Blue, true);
                            break;
                    }
                    Console.WriteLine("[{0}]:[CSXC-{1}:{2}]->{3}", count.ToString("000"), issue.IssueId.ToString("0000") ,issue.IssueType, issue.Message);
                    if (issue.SyntaxItem != null)
                        Console.WriteLine(issue.SyntaxItem);

                    count++;
                }
            }
        }

        private static void PrepareCompileInfo()
        {
            XDoc doc = XDoc.LoadFromFile(prjFile);
            doc.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003");

            outputScript = doc.GetStringValue(@"x:PropertyGroup/x:AssemblyName") + ".js";

            //Note:Project ref, get the assembly name and should be able to found ubder output(bin/debug)
            var rl = doc.NavigateToList(@"x:ItemGroup/x:ProjectReference");
            foreach (var r in rl)
            {
                var refPrjPath = r.GetStringValue(@"@Include");
                XDoc xref = XDoc.LoadFromFile(Path.Combine(startPath, refPrjPath));
                xref.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003");
                var libName = xref.GetStringValue(@"x:PropertyGroup/x:AssemblyName");
                _refPathList.Add(Path.Combine(outputDir, libName + ".dll"));
            }

            rl = doc.NavigateToList(@"x:ItemGroup/x:Reference");
            foreach (var r in rl)
            {
                string hint = r.GetStringValue(@"x:HintPath");
                if (string.IsNullOrEmpty(hint))
                {
                    //Extend:No hint, system assembly, should not occur in pure script lib, may support in future for mix type project.
                }
                else
                {
                    _refPathList.Add(Path.Combine(startPath, hint));
                }
            }

            //Note:Item to be compiled.
            var cl = doc.NavigateToList(@"x:ItemGroup/x:Compile");
            foreach (var c in cl)
            {
                string path = c.GetStringValue("@Include");
                if (path == @"Properties\AssemblyInfo.cs")
                    continue;
                _compilePathList.Add(Path.Combine(startPath, path));
            }
        }

        private static ErrorAggregator Compile()
        {
            List<SyntaxTree> treeList = new List<SyntaxTree>();
            foreach (var f in _compilePathList)
            {
                SyntaxTree sTree = SyntaxTree.ParseCompilationUnit(File.ReadAllText(f), f);
                treeList.Add(sTree);
            }

            List<MetadataReference> references = new List<MetadataReference>();
            foreach (string dir in _refPathList)
                references.Add(new AssemblyFileReference(dir));

            var c = Compilation.Create("CSXC", CompilationOptions.Default, treeList, references);
            CodeTemplate template = CodeTemplate.Parse(Path.Combine(startPath, @"Properties\template.xml"));

            TranslateDictionary dictionary = new TranslateDictionary();
            dictionary.Load(Path.Combine(startPath, @"Properties\translate.xml"));

            ErrorAggregator aggregator = new ErrorAggregator();

            IList<SyntaxTree> trees = c.SyntaxTrees;
            if (singleFile)
            {
                File.Delete(Path.Combine(outputDir, outputScript));
                trees = OrderResolver.Sort(c, aggregator);
            }

            var count = 0;
            foreach (SyntaxTree sourceTree in trees)
            {
                if (!singleFile)
                {
                    outputScript = Path.GetFileNameWithoutExtension(sourceTree.FileName) + ".js";
                    string relPath = Path.GetDirectoryName(sourceTree.FileName.RemoveBegin(startPath)).RemoveBegin("\\");

                    string outDir = Path.Combine(outputDir, relPath);
                    if (!Directory.Exists(outDir))
                        Directory.CreateDirectory(outDir);
                    outputScript = Path.Combine(relPath, outputScript);
                }

                //using (IOutput output = new ConsoleOutput())
                using (IOutput output = new FileOutput(Path.Combine(outputDir, outputScript), singleFile))
                {
                    SemanticModel model = c.GetSemanticModel(sourceTree);

                    Translator t = new Translator(model, template, dictionary, output, aggregator);

                    SyntaxNode newSource = t.Visit(sourceTree.Root);

                    count++;
                    if (!singleFile || (singleFile && count == c.SyntaxTrees.Count))
                    {
                        output.WriteLine("//# This file is compiled by CSXC from C#.");
                    }
                }
            }
            return aggregator;
        }
    }
}
